using System;
using System.Linq;
using System.Linq.Expressions;
using Magiq.Tests.Support;
using Magiq.Tests.TestModel;
using NUnit.Framework;

namespace Magiq.Tests {
    [TestFixture]
    public abstract class BaseMagiqProviderInsertsTest<T1, TI> : BaseMagiqProviderTest<T1, TI>
        where T1 : class, ITestClass1<T1>, new()
        where TI : class, ITestClass1Inheritor<T1>, T1, new() {
        protected IQueryable<T1> Destination { get; private set; }

        protected override void Initialize() {
            Destination = Configurator.GetQueryable<T1>();
        }

        private void TestInsert<T>(Expression<Func<T1, bool>> where, Expression<Func<T1, T>> insert,
                                   IQueryable<T> destination)
            where T : class, new()
        {
            var count = TestClass1s.Where(where).Count();
            var prevDestinationCount = destination.Count();
            destination.From(TestClass1s.Where(where)).Insert(insert).Should(Be.EqualTo(count));
            destination.Count().Should(Be.EqualTo(prevDestinationCount + count));
        }

        [Test]
        public void ShouldInsertAll() {
            var prevCount = Destination.Count();
            var count = TestClass1s.Count();
            var insertCount = Destination.From(TestClass1s).Insert(x => new T1
                                                                            {
                                                                                AnotherStringProperty = x.StringProperty,
                                                                                IntProperty = x.AnotherIntProperty,
                                                                                AnotherIntProperty = 4,
                                                                                StringProperty = "lala"
                                                                            });
            insertCount.Should(Be.EqualTo(count));
            Destination.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertAllIntoItself() {
            var count = TestClass1s.Count();
            TestClass1s.From(TestClass1s).Insert( x => new T1
                                                   {
                                                       AnotherStringProperty = x.StringProperty,
                                                       IntProperty = x.AnotherIntProperty,
                                                       AnotherIntProperty = 4,
                                                       StringProperty = "lala"
                                                   }).Should(Be.EqualTo(count));
            TestClass1s.Count().Should(Be.EqualTo(count*2));
        }

        [Test]
        public void ShouldInsertComplexWhere() {
            TestInsert(x => (x.StringProperty == "sarasa" && x.IntProperty >= 3) ||
                            x.AnotherIntProperty < 12 &&
                            x.AnotherStringProperty != null, x => new T1
                                                                      {
                                                                          AnotherStringProperty = x.StringProperty,
                                                                          IntProperty = x.AnotherIntProperty,
                                                                          AnotherIntProperty = 4,
                                                                          StringProperty = "lala"
                                                                      }, Destination);
        }

        [Test]
        public void ShouldInsertGroupedByAnnonymousType() {
            var items = TestClass1s.Where(x => x.AnotherIntProperty > 3)
                                   .GroupBy(x => x.AnotherStringProperty)
                                   .Select(y => new {Prop = y.Count()});
            
            TestClass3s.From(items).Insert(x => new TestClass3
                                                    {
                                                        Text = x.Prop.ToString()
                                                    });
        }

        [Test]
        public void ShouldInsertGroupedByValue() {
            var items = TestClass1s.Where(x => x.AnotherIntProperty > 3)
                                   .GroupBy(x => x.AnotherStringProperty)
                                   .Select(y => y.Count());
            TestClass3s.From(items).Insert(x => new TestClass3
                                                    {
                                                        Text = x.ToString()
                                                    });
        }

        [Test]
        public void ShouldInsertNotInheritedSubclasses() {
            var desTestClass2 = Configurator.GetQueryable<TestClass2>();
            var prevCount = desTestClass2.Count();
            var count = TestClass2s.Count();
            var insertCount = desTestClass2.From(TestClass2s).Insert(x => new TestClass2
                                                                              {
                                                                                  IntProperty = x.IntProperty + 2,
                                                                                  StringProperty = "lala",
                                                                              });
            insertCount.Should(Be.EqualTo(count));
            desTestClass2.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertOrderBy() {
            var items = TestClass1s.Where(x => x.AnotherIntProperty > 3).OrderBy(x => x.AnotherStringProperty);
            TestClass3s.From(items).Insert(x => new TestClass3
                                                    {
                                                        Text = x.AnotherStringProperty
                                                    });

            var sortedItems = TestClass3s.OrderBy(x => x.Text).ToList();
            var retrievedItems = TestClass3s.ToList();

            for (var i = 0; i < sortedItems.Count; i++)
                retrievedItems[i].Id.Should(Be.EqualTo(sortedItems[i].Id));
        }

        [Test]
        public void ShouldInsertReferences() {
            var prevCount = Destination.Count();
            var count = TestClass1s.Count();
            var insertCount = Destination.From(TestClass1s).Insert( x => new T1
                                                                     {
                                                                         AnotherStringProperty = x.StringProperty,
                                                                         IntProperty = x.AnotherIntProperty,
                                                                         AnotherIntProperty = 4,
                                                                         StringProperty = "lala",
                                                                         Reference = x.Reference
                                                                     });
            insertCount.Should(Be.EqualTo(count));
            Destination.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertSubclasses() {
            var prevCount = Destination.Count();
            var count = TestClass1s.Count();
            var insertCount = Destination.From(TestClass1s).Insert(x => new TI
                                                                            {
                                                                                AnotherStringProperty = x.StringProperty,
                                                                                IntProperty = x.AnotherIntProperty,
                                                                                AnotherIntProperty = 4,
                                                                                StringProperty = "lala",
                                                                                InheritorProperty = "testing"
                                                                            });
            insertCount.Should(Be.EqualTo(count));
            Destination.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertWhereIntoItself() {
            TestInsert(x => (x.StringProperty == "sarasa" && x.IntProperty >= 3) ||
                            x.AnotherIntProperty < 12 &&
                            x.AnotherStringProperty != null, x => new T1
                                                                      {
                                                                          AnotherStringProperty = x.StringProperty,
                                                                          IntProperty = x.AnotherIntProperty,
                                                                          AnotherIntProperty = 4,
                                                                          StringProperty = "lala"
                                                                      }, TestClass1s);
        }

        [Test]
        public void ShouldInsertWhereThatUsesAnotherIntProperty() {
            TestInsert(x => x.IntProperty == x.AnotherIntProperty, x => new T1
                                                                            {
                                                                                AnotherStringProperty =
                                                                                    x.StringProperty,
                                                                                IntProperty = x.AnotherIntProperty,
                                                                                AnotherIntProperty = 4,
                                                                                StringProperty = "lala"
                                                                            }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesAnotherStringProperty() {
            TestInsert(x => x.StringProperty == x.AnotherStringProperty, x => new T1
                                                                                  {
                                                                                      AnotherStringProperty =
                                                                                          x.StringProperty,
                                                                                      IntProperty = x.AnotherIntProperty,
                                                                                      AnotherIntProperty = 4,
                                                                                      StringProperty = "lala"
                                                                                  }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesComposedIntProperty() {
            TestInsert(x => x.IntProperty == x.AnotherIntProperty + 3, x => new T1
                                                                                {
                                                                                    AnotherStringProperty =
                                                                                        x.StringProperty,
                                                                                    IntProperty = x.AnotherIntProperty,
                                                                                    AnotherIntProperty = 4,
                                                                                    StringProperty = "lala"
                                                                                }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesComposedStringProperty() {
            TestInsert(x => x.StringProperty == x.AnotherStringProperty + "la", x => new T1
                                                                                         {
                                                                                             AnotherStringProperty =
                                                                                                 x.StringProperty,
                                                                                             IntProperty =
                                                                                                 x.AnotherIntProperty,
                                                                                             AnotherIntProperty = 4,
                                                                                             StringProperty = "lala"
                                                                                         }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesIntProperty() {
            TestInsert(x => x.IntProperty == 3, x => new T1
                                                         {
                                                             AnotherStringProperty =
                                                                 x.StringProperty,
                                                             IntProperty = x.AnotherIntProperty,
                                                             AnotherIntProperty = 4,
                                                             StringProperty = "lala"
                                                         }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesNullableIntProperty() {
            TestInsert(x => x.NullableIntProperty == 3,
                       x =>
                       new T1
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesNullableIntPropertyNull() {
            TestInsert(x => x.NullableIntProperty == null,
                       x =>
                       new T1
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }


        [Test]
        public void ShouldInsertWhereThatUsesNullableIntPropertyToNull() {
            TestInsert(x => x.NullableIntProperty == null,
                       x =>
                       new T1
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesNullableIntPropertyWithIntProperty() {
            TestInsert(x => x.NullableIntProperty == x.IntProperty,
                       x =>
                       new T1
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesReference() {
            var r = TestClass2s.First();
            TestInsert(x => x.Reference == r, x => new T1
                                                       {
                                                           AnotherStringProperty =
                                                               x.StringProperty,
                                                           IntProperty = x.AnotherIntProperty,
                                                           AnotherIntProperty = 4,
                                                           StringProperty = "lala"
                                                       }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesReferenceNull() {
            TestInsert(x => x.Reference == null,
                       x =>
                       new T1
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesSelfReference() {
            TestInsert(x => x.SelfReference != null && x.SelfReference.IntProperty > 3,
                       x =>
                       new T1
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesStringProperty() {
            TestInsert(x => x.StringProperty == "sarasa", x => new T1
                                                                   {
                                                                       AnotherStringProperty = x.StringProperty,
                                                                       IntProperty = x.AnotherIntProperty,
                                                                       AnotherIntProperty = 4,
                                                                       StringProperty = "lala"
                                                                   }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesStringPropertyNull() {
            TestInsert(x => x.StringProperty == null, x => new T1
                                                               {
                                                                   AnotherStringProperty =
                                                                       x.StringProperty,
                                                                   IntProperty = x.AnotherIntProperty,
                                                                   AnotherIntProperty = 4,
                                                                   StringProperty = "lala"
                                                               }, Destination);
        }
        }
}